Soulbound Token
Non-Fungible かつ Non-Transferable なトークン。運転免許証や卒業証書などの各種の証明書や POAP のような参加証明などによくマッチしそうなインターフェイスのトークン。
Decentralized Society: Finding Web3's Soul by E. Glen Weyl, Puja Ohlhaver, Vitalik Buterin :: SSRN
https://vitalik.eth.limo/general/2022/01/26/soulbound.html
https://www.notion.so/WG-Token-Notion-718e353618c14beeb910f5eb92a663b0
EIP-4973: Account-bound Tokens
EIP-5114: Soulbound Badge
EIP-5192: Minimal Soulbound NFTs
----
Wagumi の Soulbound Token の実装例
https://polygonscan.com/address/0xef756b67b90026f91d047d1b991f87d657309a42#code
code:solidity
/**
*Submitted for verification at polygonscan.com on 2022-09-08
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
library SbtLib {
bytes32 constant SBT_STRUCT_POSITION = keccak256("xyz.wagumi.sbt");
struct SbtStruct {
address contractOwner;
string name;
string symbol;
string baseURI;
bytes32 validator;
mapping(bytes4 => bool) interfaces;
mapping(address => uint256) balances;
mapping(uint256 => address) owners;
mapping(uint256 => SbbStruct[]) sbbs;
mapping(bytes32 => uint256) sbbIndex;
}
struct SbbStruct {
uint256 chainId;
address contractAddress;
uint256 tokenId;
}
function sbtStorage()
internal
pure
returns (SbtStruct storage sbtstruct)
{
bytes32 position = SBT_STRUCT_POSITION;
assembly {
sbtstruct.slot := position
}
}
}
// File: diamond/Sbt.sol
pragma solidity ^0.8.16.0;
contract Sbt {
function init(
address _contractOwner,
string calldata _name,
string calldata _symbol,
string calldata _baseURI,
bytes32 _validator
) external {
SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
require(sbtstruct.contractOwner == address(0), "INITED ALREADY");
sbtstruct.contractOwner = _contractOwner;
sbtstruct.name = _name;
sbtstruct.symbol = _symbol;
sbtstruct.baseURI = _baseURI;
sbtstruct.validator = _validator;
sbtstruct.interfaces(bytes4)(0x01ffc9a7) = true; //ERC165
sbtstruct.interfaces(bytes4)(0x5b5e139f) = true; //ERC721metadata
}
mapping(bytes4 => address) public implementations;
function setImplementation(
bytes4[] calldata _sigs,
address[] calldata _impAddress
) external {
SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
require(msg.sender == sbtstruct.contractOwner, "OWNER ONLY");
require(_sigs.length == _impAddress.length, "INVAILED LENGTH");
for (uint256 i = 0; i < _sigs.length; i++) {
unchecked {
implementations[_sigsi] = _impAddressi;
}
}
}
function contractOwner() external view returns (address) {
SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
return sbtstruct.contractOwner;
}
function supportsInterface(bytes4 _interfaceID)
external
view
returns (bool)
{
SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
return sbtstruct.interfaces_interfaceID;
}
function name() external view returns (string memory) {
SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
return sbtstruct.name;
}
function symbol() external view returns (string memory) {
SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
return sbtstruct.symbol;
}
function tokenURI(uint256 _tokenId) external view returns (string memory) {
SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
return
string(
abi.encodePacked(sbtstruct.baseURI, toString(_tokenId), ".json")
);
}
function ownerOf(uint256 _tokenId) external view returns (address) {
SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
return sbtstruct.owners_tokenId;
}
function toString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
unchecked {
digits++;
temp /= 10;
}
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
unchecked {
digits -= 1;
bufferdigits = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
}
return string(buffer);
}
fallback() external payable {
address _imp = implementationsmsg.sig;
require(_imp != address(0), "Function does not exist");
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), _imp, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
receive() external payable {}
}